﻿using System;
using System.Reflection;
using TestByWire.AttributeHandlers;
using Microsoft.Practices.Unity;

namespace TestByWire
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = true)]
    public sealed class UseStubLateBoundAttribute : Attribute, IContainerInteraction
    {
        private readonly Type _fieldType;
        private readonly string _fieldName;
        private readonly Type _stubType;

        public UseStubLateBoundAttribute(Type fieldType, String fieldName, Type stubType)
        {
            _fieldType = fieldType;
            _fieldName = fieldName;
            _stubType = stubType;
        }

        public UseStubLateBoundAttribute(Type fieldType, String fieldName)
            : this(fieldType, fieldName, null)
        {
        }

        Type IContainerInteraction.RegistrationType
        {
            get
            {
                return _fieldType;
            }
        }

        string IContainerInteraction.RegistrationName
        {
            get
            {
                return _fieldName;
            }
        }

        public string ContainerRegistrationName
        {

            get { return _fieldName; }
        }

        public void Execute(IBuildContext buildContext)
        {
            if (buildContext == null)
            {
                throw new ArgumentNullException("buildContext");
            }

            if (buildContext.TestTarget == null)
            {
                return;
            }

            var container = buildContext.Container;
            if (container == null)
            {
				throw new ArgumentException("Container property is not set", "buildContext");
            }

            Type testTargetType = buildContext.TestTarget.GetType();
            FieldInfo fieldInfo = GetFieldInfo(testTargetType, _fieldName);
            if (fieldInfo == null)
                throw new InvalidOperationException(string.Format("Field: {0}, does not exist on Test Target: {1}",
                                                                  _fieldName, testTargetType));
            var registrationName = ((IContainerInteraction)this).RegistrationName;
            if (_stubType != null)
            {
                var valueToInject = Activator.CreateInstance(_stubType);
                fieldInfo.SetValue(buildContext.TestTarget, valueToInject);


                container.RegisterInstance(_fieldType, registrationName, valueToInject);
            }
            else
            {
                if (container.IsRegistered(_fieldType, registrationName))
                {
                    throw new InvalidOperationException("The field " + _fieldName + " is injecting an unnamed type which is already in use.");
                }

                var valueToInject = container.Resolve(_fieldType, registrationName);
                fieldInfo.SetValue(buildContext.TestTarget, valueToInject);
            }
        }

        public Type ImplementationType
        {
            get { return _stubType; }
        }

        public string FieldName
        {
            get { return _fieldName; }
        }

        private static FieldInfo GetFieldInfo(Type targetInstanceType, string fieldName)
        {
            var fieldInfo = targetInstanceType.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
            if (fieldInfo == null && targetInstanceType.BaseType != typeof(Object))
                return GetFieldInfo(targetInstanceType.BaseType, fieldName);

            return fieldInfo;
        }
    }

}
